#include "tabs.h"
#include "tabs_win32.h"
#include "widget_helper_win32.h"
#include "vector2.h"
#include "signal_slot.h"

#include <iostream>
#include <unordered_set>

using namespace krryn::core;
using namespace krryn::math;
using namespace krryn::system_imp;
using namespace krryn::gui;
using namespace Gdiplus;

// TODO:
//  - Sort by MRU (increment counter when tab is activated)
//  - Sort by when a tab has been opened
//
//  - Make it look good
//  - Allow right clicking the tab bar
//	   - Display a menu to close, close right, close all but this one and some context sensitive info.
//
//  - How do we handle overflowing the tab?
//     - Dropdown
//     - Left / right buttons
//  - Do we implement docking? If so, how?
//  - Do we allow the tab to be inside a new window? (Multi montior support)
//     - If we do that, we probably need to relocate the HWND to another window
//       this means adding support for a non-destructive remove_child() function
//     - Could we also do this internally and create our own window?

// DONE:
//  - Draw tab bar
//     - Highlight active tab
//     - Close button
//  - Also, all childs need a title, how are we going to manage that?

namespace{
	char* tabs_class(){
		static bool m_IsRegistered = false;

		if(!m_IsRegistered){
			WNDCLASS l_WindowClass;
			memset(&l_WindowClass, 0, sizeof(l_WindowClass));

			l_WindowClass.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
			l_WindowClass.lpfnWndProc	= (WNDPROC)DefWindowProc;
			l_WindowClass.hIcon			= LoadIcon(NULL, IDI_WINLOGO);
			l_WindowClass.hCursor		= LoadCursor(NULL, IDC_ARROW);
			l_WindowClass.lpszClassName	= "krryn::tabs_class";

			if(!RegisterClass(&l_WindowClass)){
				throw widget_exception("Can't register window class");
			}else{
				m_IsRegistered = true;
			}
		}

		return "krryn::tabs_class";
	}

	RECT rectf_to_rect(RectF r){
		RECT t = {(INT)r.X, (INT)r.Y, (INT)r.X + r.Width, (INT)r.Y + r.Height};
		return t;
	}
}

void tab_panel_impl_win32::make(tabs *a_Parent){
	widget_hook_win32(this);

	m_hWnd = CreateWindow(
		TEXT("STATIC"), 
		TEXT(""),
		WS_CHILD, 
		0, 0, 
		800, 600,
		(HWND)a_Parent->handle(), 0, 0, 0);
}

void tabs_impl_win32::make(const tabs_initializer &a_Initializer){
	widget_helper_win32::load_common_controls();
	HWND l_ParentWnd = (HWND)a_Initializer.get_parent()->handle();

	{
		widget_hook_win32(this);

		unsigned int l_TabHeight = 26;
		math::size l_ParentSize = a_Initializer.get_parent()->get_size();
		
		m_hWndTabs = CreateWindowEx(
			WS_EX_COMPOSITED,
			tabs_class(),
			TEXT(""), 
			widget_helper_win32::visible(a_Initializer) | WS_CHILD,
			0, 0,
			l_ParentSize.m_Width, l_TabHeight,
			l_ParentWnd, 0, 0, 0);

		m_hWnd = CreateWindow(
			TEXT("STATIC"), 
			TEXT(""),
			widget_helper_win32::visible(a_Initializer) | WS_CHILD, 
			0, l_TabHeight + a_Initializer.get_top(), 
			l_ParentSize.m_Width, l_ParentSize.m_Height - l_TabHeight, 
			l_ParentWnd, 0, 0, 0);
	}
}

void tabs_impl_win32::on_child_resize(resize_args *a_Args){

}

void tabs_impl_win32::add_child(widget &a_Widget){
	tabs_impl_base::add_child(a_Widget);
	activate_last();
}

void tabs_impl_win32::remove_child(size_t i){
	tabs_impl_base::remove_child(i);
	activate_last();

	auto e = m_VisibleTabs.find(i);
	if(e != m_VisibleTabs.end()){
		m_VisibleTabs.erase(e);
	}
}

void tabs_impl_win32::activate_last(){
	m_ActiveTab = count() - 1;
	if(count() > 1){
		get_child(count() - 2).set_visible(false);
	}
	if(count() > 0){
		get_child(count() - 1).set_visible(true);
	}
}

void tabs_impl_win32::set_visible(bool a_IsVisible){
	widget_win32::set_visible(a_IsVisible);
	widget_helper_win32::set_visible(m_hWndTabs, a_IsVisible);
}

unsigned int tabs_impl_win32::draw_tab(Graphics &g, unsigned int a_X, unsigned int a_Y, unsigned int a_Index){
	if(a_X > get_size().m_Width) return a_X;

	Color c = Color(255, 44, 61, 91);

	if(m_ActiveTab == a_Index) c = Color(255, 255, 232, 166);
	else if(m_HoverTab == a_Index) c = Color(255, 100, 110, 117);

	SolidBrush b(c);
	SolidBrush t(m_ActiveTab == a_Index ? Color(255, 255, 248, 225) : c);
	SolidBrush black(Color(255, 0, 0, 0));
	SolidBrush white(Color(255, 255, 255, 255));
	SolidBrush closeDark(Color(255, 117, 99, 61));
	SolidBrush closeLight(Color(255, 206, 212, 221));
	Pen p(Color(255, 0, 0, 0));
	Font font(L"Tahoma", 10);
	PointF origin(a_X, a_Y), originX(a_X, a_Y);

	RectF bb, close;

	std::string s = this->get_child_name(a_Index);
	std::wstring w(s.begin(), s.end());

	// draw tab
	g.MeasureString(w.c_str(), w.size(), &font, origin, &bb);
	bb.Height = 20;
	originX.X += bb.Width + 5;
	g.MeasureString(L"X", 2, &font, originX, &close);
	close.Width += 5;
	RectF::Union(bb, bb, close);


	g.FillRectangle(&t, a_X, a_Y + 0.f, bb.Width, 10.f);
	g.FillRectangle(&b, a_X, a_Y + 10.f, bb.Width, 10.f);

	g.DrawString(w.c_str(), w.size(), &font, origin, m_ActiveTab == a_Index ? &black : &white);

	// draw close button
	if(m_ActiveTab == a_Index || m_HoverTab == a_Index){
		g.DrawString(L"X", 2, &font, originX, m_ActiveTab == a_Index ? &closeDark : &closeLight);
	}

	m_VisibleTabs[a_Index].m_BoundingBox = bb;
	m_VisibleTabs[a_Index].m_CloseBox    = close;

	return bb.Width;
}

void tabs_impl_win32::wm_paint_tabs(HWND hWnd){
	PAINTSTRUCT l_Paint;
	HDC hDC = BeginPaint(hWnd, &l_Paint);
	Graphics g(hDC);
	//g.SetSmoothingMode(SmoothingModeAntiAlias);

	unsigned int l_X = 0;
	m_VisibleTabs.clear();
	for(unsigned int i = 0; i < count(); i++){
		l_X += draw_tab(g, l_X, 2, i);
	}

	EndPaint(hWnd, &l_Paint);
}

LRESULT tabs_impl_win32::process_message(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){
	if(hWnd == m_hWndTabs){
		if(uMsg == WM_ERASEBKGND){
			Graphics g((HDC)wParam);

			// render background
			SolidBrush b(Color(255, 44, 61, 91));
			g.FillRectangle(&b, 0, 0, get_size().m_Width, get_size().m_Height);
			
			SolidBrush yellow(Color(255, 255, 232, 166));
			g.FillRectangle(&yellow, 0, 22, get_size().m_Width, 3);

			return 0;
		}else if(uMsg == WM_PAINT){
			wm_paint_tabs(hWnd);
			return 0;
		}else if(uMsg == WM_MOUSEMOVE){
			// TODO: don't do this test in WM_MOUSEMOVE, do it in WM_PAINT
			unsigned int oldHover = m_HoverTab;
			m_HoverTab = (unsigned int)-1;
			for(auto i = m_VisibleTabs.begin(); i != m_VisibleTabs.end(); i++){
				bool inside = i->second.m_BoundingBox.Contains(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
				if(inside){
					m_HoverTab = i->first;
					break;
				}
			}

			if(m_HoverTab != oldHover){
				::InvalidateRect(m_hWndTabs, 0, TRUE);
			}

			return 0;
		}else if(uMsg == WM_LBUTTONDOWN){
			unsigned int oldActive = m_ActiveTab;

			for(auto i = m_VisibleTabs.begin(); i != m_VisibleTabs.end(); i++){
				bool inside = i->second.m_BoundingBox.Contains(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
				bool close = i->second.m_CloseBox.Contains(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));

				if(close){
					remove_child(i->first);
					::InvalidateRect(m_hWndTabs, NULL, TRUE);
					::InvalidateRgn(m_hWnd, NULL, TRUE);

					// remove_child invalidates m_VisibleTabs iterator
					break;
				}else{
					get_child(i->first).set_visible(inside);

					if(inside){
						m_ActiveTab = i->first;
					}
				}
			}

			if(m_ActiveTab != oldActive){
				::InvalidateRgn(m_hWnd, NULL, TRUE);
				::InvalidateRgn(m_hWndTabs, NULL, TRUE);
			}

			return 0;
		}else if(uMsg == WM_MOUSELEAVE){
			m_HoverTab = (unsigned)-1;
			::InvalidateRect(m_hWndTabs, NULL, TRUE);
		}
	}

	return widget_win32::process_message(hWnd, uMsg, wParam, lParam);
}